{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果给定一个线程，只要令`std::thread` 对象与之关联，就能管控该线程的几乎每个细节"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.1 线程的基本管控"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "每个 C++程序都含有至少一个线程，即运行 `main()`的线程，它由 C++运行时（C++ runtime）系统启动。随后，程序就可以发起更多线程，它们以别的函数作为入口（entry point）。这些新线程连同起始线程并发运行。当 `main()`返回时，程序就会退出；同样，当入口函数返回时，对应的线程随之终结。我们会看到，如果借 `std::thread` 对象管控线程，即可选择等它自然结束。不过，首先要让线程启动，所以我们来学习发起线程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 发起线程 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void do_some_work(); \n",
    "\n",
    "std::thread my_thread(do_some_work); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与 C++标准库中的许多类型相同，任何可调用类型（[callable type](https://en.cppreference.com/w/cpp/named_req/Callable)）都适用于`std::thread`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// https://godbolt.org/z/f9aW5dEas\n",
    "\n",
    "#include <iostream>\n",
    "#include <thread>\n",
    "\n",
    "// 带有函数调用操作符（function call operator）的类\n",
    "class background_task {\n",
    "   public:\n",
    "    void do_something() const {}\n",
    "    void do_something_else() const {}\n",
    "\n",
    "    void operator()() const {\n",
    "        do_something();\n",
    "        do_something_else();\n",
    "    }\n",
    "};\n",
    "\n",
    "int main() {\n",
    "    background_task f;\n",
    "    std::thread my_thread(f); // f被复制到属于新线程的存储空间中，并在那里被调用，由新线程执行。\n",
    "                              // 副本的行为必须与原本的函数对象等效，否则运行结果可能有违预期。\n",
    "\n",
    "    my_thread.join();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++’s most vexing parse: 针对存在二义性的C++语句，只要它有可能被解释成函数声明，编译器就肯定将其解释成函数声明。\n",
    "\n",
    "例如，语句“`std::thread my_thread(background_task());`”的本意是发起新线程，却被解释成函数声明：函数名是 `my_thread`，只接收一个参数，返回`std::thread` 对象，接收的参数是函数指针，所指向的函数没有参数传入，返回`background_task` 对象；该指针是对 `background_task()`的不当解释，其原意是生成临时的匿名函数对象。为临时函数对象命名即可解决问题，做法是多用一对圆括号，或采用新式的统一初始化语法（uniform initialization syntax，又名列表初始化），例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::thread my_thread((background_task())); // 加入了额外的圆括号，以防语句被解释成函数声明\n",
    "\n",
    "std::thread my_thread{background_task()}; // 统一初始化语法（uniform initialization syntax，又名列表初始化）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为避免这种问题，我们还能采用 lambda 表达式。它是 C++11 的新特性，属于可调用对象，它准许我们编写局部函数，能捕获某些局部变量，又无须另外传递参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::thread my_thread([]{do_something();  do_something_else();}); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "一旦启动了线程，我们就需明确是要等待它结束（与之汇合，见 2.1.2 节），还是任由它独自运行（与之分离，见 2.1.3 节）。假如等到 `std::thread` 对象销毁之际还没决定好，那 `std::thread` 的析构函数将调用 `std::terminate()`终止整个程序。\n",
    "\n",
    "所以，当务之急是设定新线程，使之正确地汇合或分离，即便有异常抛出也照样如此。2.1.3 节将介绍一种技法，以应付发生异常的情形。\n",
    "\n",
    "请注意，我们只需在 `std::thread` 对象销毁前做出决定—线程本身可能在汇合或分离前就早已结束。如果选择了分离，且分离时新线程还未结束运行，那它将继续运行，甚至在 `std::thread` 对象销毁很久之后依然运行，它只有最终从线程函数返回时才会结束运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假定程序不等待线程结束，那么在线程运行结束前，我们需保证它所访问的外部数据始终正确、有效。这并非新问题，在单线程代码中，试图访问已销毁的对象同样是未定义的行为。不过，因为使用多线程，所以我们可能会经常面临这种生存期问题。\n",
    "\n",
    "以下情形会诱发该问题：新线程上的函数持有指针或引用，指向主线程的局部变量；但主线程所运行的函数退出后，新线程却还没结束。代码清单 2.1 再现了这一情形。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.1 \n",
    "// https://godbolt.org/z/nfs47rP9s\n",
    "\n",
    "#include <iostream>\n",
    "#include <thread>\n",
    "\n",
    "struct func {\n",
    "    int& i;\n",
    "    func(int& i_) : i(i_) {}\n",
    "    void operator()() {\n",
    "        for (unsigned j = 0; j < 1000000; ++j) {\n",
    "            do_something(i);  // ①隐患: 可能访问悬空引用\n",
    "        }\n",
    "    }\n",
    "\n",
    "    void do_something(int i) {}\n",
    "};\n",
    "\n",
    "void oops() {\n",
    "    int some_local_state = 0;\n",
    "    func my_func(some_local_state);\n",
    "    std::thread my_thread(my_func);\n",
    "    my_thread.detach();  // ②不等待新线程结束\n",
    "}  // ③新线程可能仍在运行，而主线程的函数却已结束\n",
    "\n",
    "int main() { oops(); }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在本例中调用 `detach()`，明确设定不等待。于是在 `oops()`退出后，与 `my_thread`对象关联的新线程很可能仍继续运行。如果新线程继续运行，表 2.1 所示的情形就会出现：`do_something()`的下一次调用会访问已经被销毁的局部变量。和普通的单线程代码一样，在函数退出后，程序依然准许指针或引用持续指向其局部变量，这绝不是好的构思，然而多线程代码更容易导致这种错误，因为即使出现了以上情形，错误也不一定立刻发生。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 主 线 程                                         | 新 线 程                                                     |\n",
    "| ------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| 构造 `my_func` 对象，引用局部变量 `some_local_state` |                                                              |\n",
    "| 通过 `my_thread` 对象启动新线程                    |                                                              |\n",
    "|                                                  | 新线程启动                                                   |\n",
    "|                                                  | 调用 `func::operator()`                                        |\n",
    "|                                                  | 运行 `func::operator();` <br>调用 `do_something()`函数，<br>进而引用局部变量 `some_local_state` |\n",
    "| 分离新线程 `my_thread`                             |                                                              |\n",
    "| 销毁局部变量 `some_local_state`                    | 仍在运行                                                     |\n",
    "| 退出 `oops()`                                      | 继续运行 `func::operator();` <br>调用 `do_something()`函数，<br>进而引用 `some_local_state`，<br>导致未定义行为 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上述情形的处理方法通常是：令线程函数完全自含（self-contained），将数据复制到新线程内部，而不是共享数据。若由可调用对象完成线程函数的功能，那它就会完整地将数据复制到新线程内部，因此原对象即使立刻被销毁也无碍。然而，如果可调用对象含有指针或引用（类似代码清单 2.1 的代码），那我们仍需谨慎行事。\n",
    "\n",
    "具体而言，以下做法极不可取：意图在函数中创建线程，并让线程访问函数的局部变量。除非线程肯定会在该函数退出前结束，否则切勿这么做。\n",
    "\n",
    "处理上述情形的另一种方法是汇合新线程，此举可以确保在主线程的函数退出前，新线程执行完毕。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 等待线程完成 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若需等待线程完成，那么可以在与之关联的 `std::thread` 实例上，通过调用成员函数`join()`实现。对于代码清单 2.1 的代码，只要把调用 `my_thread.detach()`替换为调用 `my_thread.join()`，就足以确保在函数`oops()`退出前（局部变量被销毁前），新线程会结束。\n",
    "\n",
    "就该例而言，利用独立线程运行可调用对象的意义不大，原因是在此期间，主线程根本未能有效工作。但在现实代码中，原始线程有可能需要完成自己的工作，也有可能发起好几个线程，令它们各自承担实质工作，而原始线程则等待它们全部完成。\n",
    "\n",
    "`join()`简单而粗暴，我们抑或一直等待线程结束，抑或干脆完全不等待。如需选取更精细的粒度控制线程等待，如查验线程结束与否，或限定只等待一段时间，那我们便得改用其他方式，如条件变量和`future`，我们将在第 4 章进行详细介绍。\n",
    "\n",
    "只要调用了 `join()`，隶属于该线程的任何存储空间即会因此清除，`std::thread` 对象遂不再关联到已结束的线程。事实上，它与任何线程均无关联。其中的意义是，对于某个给定的线程，`join()`仅能调用一次；只要 `std::thread` 对象曾经调用过 `join()`，线程就不再可汇合（joinable），成员函数 `joinable()`将返回`false`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 在出现异常的情况下等待 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如前文所述，在 `std::thread` 对象被销毁前，我们需确保已经调用 `join()`或 `detach()`。通常，若要分离线程，在线程启动后调用 `detach()`即可，这不成问题。然而，假使读者打算等待线程结束，则需小心地选择执行代码的位置来调用 `join()`。原因是，如果线程启动以后有异常抛出，而`join()`尚未执行，则该`join()`调用会被略过。\n",
    "\n",
    "为了防止因抛出异常而导致的应用程序终结，我们需要决定如何处理这种情况。一般地，若调用`join()`仅仅是为了处理没有出现异常的情况，那么万一发生异常，我们也仍需调用 `join()`，以避免意外的生存期问题。代码清单 2.2 展示了一段简单的代码，它遵从上述要求。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.2\n",
    "// https://godbolt.org/z/17sYfzMYE\n",
    "\n",
    "#include <iostream>\n",
    "#include <thread>\n",
    "\n",
    "struct func {\n",
    "    int& i;\n",
    "    func(int& i_) : i(i_) {}\n",
    "    void operator()() {\n",
    "        for (unsigned j = 0; j < 1000000; ++j) {\n",
    "            do_something(i);  // ①隐患: 可能访问悬空引用\n",
    "        }\n",
    "    }\n",
    "\n",
    "    void do_something(int i) {}\n",
    "};\n",
    "\n",
    "void do_something_in_current_thread() {}\n",
    "\n",
    "void f()\n",
    "{\n",
    "    int some_local_state=0;\n",
    "    func my_func(some_local_state);\n",
    "    std::thread t(my_func);\n",
    "    try\n",
    "    {\n",
    "        do_something_in_current_thread();\n",
    "    }\n",
    "    catch(...)\n",
    "    {\n",
    "        t.join();  // 这行代码被黄色高亮显示\n",
    "        throw;\n",
    "    }\n",
    "    t.join();\n",
    "}\n",
    "\n",
    "int main() { f(); }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在代码清单 2.2 中，新线程依然访问 `f()`内的局部变量 `some_local_state`，而 `try/catch`块的使用则保证了新线程在函数 `f()`退出前终结（无论 `f()`是正常退出还是因为异常而退出）。`try/catch` 块稍显冗余，还容易引发作用域的轻微错乱，故它并非理想方案。假如代码必须确保新线程先行结束，之后当前线程的函数才退出（无论是因为新线程持有引用，指向函数内的局部变量，还是其他缘故），那么关键就在于，全部可能的退出路径都必须保证这种先后次序，无论是正常退出，还是抛出异常。为此，这值得我们给出简洁、有效的实现方式。\n",
    "\n",
    "为达成前面的目标，我们可以设计一个类，运用标准的 RAII 手法，在其析构函数中调用 `join()`，代码如下。请观察代码清单 2.3 如何简化函数 `f()`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.3\n",
    "// https://godbolt.org/z/EvvEcerj3\n",
    "\n",
    "#include <thread>\n",
    "\n",
    "class thread_guard {\n",
    "    std::thread& t; // 使用引用而不是值传递，可以避免对std::thread对象的复制（实际上std::thread是不可复制的，只能移动）\n",
    "\n",
    "   public:\n",
    "    explicit thread_guard(std::thread& t_) : t(t_) {}\n",
    "    ~thread_guard() {\n",
    "        if (t.joinable()) { // 1\n",
    "            t.join(); // 2\n",
    "        }\n",
    "    }\n",
    "    thread_guard(thread_guard const&) = delete; // 3\n",
    "    thread_guard& operator=(thread_guard const&) = delete;\n",
    "}; // 4\n",
    "\n",
    "void do_something(int& i) { ++i; }\n",
    "\n",
    "struct func {\n",
    "    int& i;\n",
    "\n",
    "    func(int& i_) : i(i_) {}\n",
    "\n",
    "    void operator()() {\n",
    "        for (unsigned j = 0; j < 1000000; ++j) {\n",
    "            do_something(i);\n",
    "        }\n",
    "    }\n",
    "};\n",
    "\n",
    "void do_something_in_current_thread() {}\n",
    "\n",
    "void f() {\n",
    "    int some_local_state;\n",
    "    func my_func(some_local_state);\n",
    "    std::thread t(my_func);\n",
    "    thread_guard g(t);\n",
    "\n",
    "    do_something_in_current_thread();\n",
    "}\n",
    "\n",
    "int main() { f(); }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当主线程执行到 `f()`末尾时，按构建的逆序，全体局部对象都会被销毁。因此类型`thread_guard` 的对象 `g` 首先被销毁，在其析构函数中，新线程汇合`②`。即便 `do_something_ in_current_thread()`抛出异常，函数 `f()`退出，以上行为仍然会发生。\n",
    "\n",
    "在代码清单 2.3 中，`thread_guard` 的析构函数先调用 `joinable()`①，判别 `std::thread` 对象能否汇合，接着才调用 `join()`②。该检查必不可少，因为在任何执行线程（thread of execution）上，`join()`只能被调用一次，假若线程已经汇合过，那么再次调用 `join()`则是错误行为。\n",
    "\n",
    "复制构造函数和复制赋值操作符都以“`=delete`”标记③，限令编译器不得自动生成相关代码。复制这类对象或向其赋值均有可能带来问题，因为所产生的新对象的生存期也许更长，甚至超过了与之关联的线程。在销毁原对象和新对象时，分别发生两次析构，将重复调用 `join()`。只要把它们声明为删除函数，试图复制 `thread_guard` 对象就会产生编译错误。关于删除函数请参考附录 A.2 节。\n",
    "\n",
    "若不需要等待线程结束，我们可以将其分离，从而防止异常引发的安全问题。分离操作会切断线程和 `std::thread` 对象间的关联。这样，`std::thread` 对象在销毁时肯定不调用`std::terminate()`，即便线程还在后台运行，也不会调用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 在后台运行线程 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "调用 `std::thread` 对象的成员函数 `detach()`，会令线程在后台运行，遂无法与之直接通信。假若线程被分离，就无法等待它完结，也不可能获得与它关联的 `std::thread` 对象，因而无法汇合该线程。然而分离的线程确实仍在后台运行，其归属权和控制权都转移给C++运行时库（runtime library，又名运行库），由此保证，一旦线程退出，与之关联的资源都会被正确回收。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "UNIX 操作系统中，有些进程叫作**守护进程**（daemon process），它们在后台运行且没有对外的用户界面；沿袭这一概念，分离出去的线程常常被称为守护线程（daemon thread）。这种线程往往长时间运行。几乎在应用程序的整个生存期内，它们都一直运行，以执行后台任务，如文件系统监控、从对象缓存中清除无用数据项、优化数据结构等。另有一种模式，就是由分离线程执行“启动后即可自主完成”（a fire-and-forget task）的任务；我们还能通过分离线程实现一套机制，用于确认线程完成运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如 2.1.2 节所介绍的，若要分离线程，则需在 `std::thread` 对象上调用其成员函数`detach()`。调用完成后，`std::thread` 对象不再关联实际的执行线程，故它变得从此不可汇合。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::thread t(do_background_work); \n",
    "t.detach(); \n",
    "assert(!t.joinable()); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果要把 `std::thread` 对象和线程分离，就必须存在与其关联的执行线程：若没有与其关联的执行线程，便不能在 `std::thread` 对象上凭空调用 `detach()`。这与调用 `join()`的前提条件毫无二致，检查方法完全相同，只有当 `t.joinable()`返回 `true` 时，我们才能调用`t.detach()`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(⊙▃⊙ 懵了)\n",
    "\n",
    "考虑一个应用程序，如文字处理软件。为了令它同时编辑多个文件，在用户界面层面和内部层面都有多种实现方式。一种常见的做法是，创建多个独立的顶层窗口，分别与正在编辑的文件逐一对应。尽管这些窗口看起来完全独立，各有自己的选项单，但它们其实都在同一应用程序的实例中运行。相应的内部处理是，每个文件的编辑窗口都在各自线程内运行；每个线程运行的代码相同，而处理的数据有别，因为这些数据与各文件和对应窗口的属性关联。按此，打开一个新文件就需启动一个新线程。新线程只处理打开新文件的请求，并不牵涉等待其他线程的运行和结束。对其他线程而言，该文件由新线程负责，与它们无关。综上，运行分离线程就成了首选方案。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **多线程文档编辑概念**\n",
    "> \n",
    "> 正如中文文本所解释的：\n",
    "> - 每个文档窗口看起来是独立的，有自己的菜单选项，但它们都在同一个应用程序实例中运行\n",
    "> - 在内部，每个文档编辑窗口都在自己的线程中运行\n",
    "> - 所有线程运行相同的代码，但操作不同的数据（与不同文件相关联的数据）\n",
    "> - 打开新文件需要启动新线程\n",
    "> - 新线程只处理打开新文件的请求，不涉及等待其他线程的运行和结束\n",
    "> - 对于其他线程而言，新文件由新线程负责，与它们无关\n",
    "> - 使用分离线程是这种情况下的首选方案\n",
    "> \n",
    "> 这种架构允许应用程序同时处理多个文档，不会阻塞用户界面，即使在处理多个文件时也能提供响应迅速的体验。\n",
    "> \n",
    "> 分离线程方法在这里特别有用，因为：\n",
    "> - 每个文档线程可以独立操作\n",
    "> - 应用程序不需要等待一个文档操作完成后再处理另一个\n",
    "> - 它实现了文档的真正并行处理\n",
    "> \n",
    "> 这是文字处理器、电子表格和IDE等基于文档的应用程序中常见的设计模式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码清单 2.4 列出一段简单代码，简略示范了上述方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.4 分离线程以处理新文件\n",
    "// https://godbolt.org/z/hxa3d4c5r\n",
    "\n",
    "#include <string>\n",
    "#include <thread>\n",
    "\n",
    "void open_document_and_display_gui(std::string const& filename) {}\n",
    "\n",
    "bool done_editing() { return true; }\n",
    "\n",
    "enum command_type { open_new_document };\n",
    "\n",
    "struct user_command {\n",
    "    command_type type;\n",
    "\n",
    "    user_command() : type(open_new_document) {}\n",
    "};\n",
    "\n",
    "user_command get_user_input() { return user_command(); }\n",
    "\n",
    "std::string get_filename_from_user() { return \"foo.doc\"; }\n",
    "\n",
    "void process_user_input(user_command const& cmd) {}\n",
    "\n",
    "void edit_document(std::string const& filename) {\n",
    "    open_document_and_display_gui(filename);\n",
    "    while (!done_editing()) {\n",
    "        user_command cmd = get_user_input();\n",
    "        if (cmd.type == open_new_document) {\n",
    "            std::string const new_name = get_filename_from_user();\n",
    "            std::thread t(edit_document, new_name); // 1\n",
    "            t.detach(); // 2\n",
    "        } else {\n",
    "            process_user_input(cmd);\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "int main() { edit_document(\"bar.doc\"); }\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果用户选择打开一个新文件，那就提示他们输入文件名，开启新线程打开该文件①，然后分离②。与当前线程一样，新线程只是对不同文件进行相同的操作，因此，只要向函数传入选定的新文件名作为参数，就能复用 `edit_document()`函数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本例还展示了在新线程启动时向函数传递参数：我们向 `std::thread` 的构造函数传递的参数中，除了线程函数的名字，还有目标文件名①。虽然我们可以不采用接收参数的普通函数，而通过其他方式传递参数，譬如采用带有数据成员的函数对象，但还是 C++标准库提供的方法最为简便。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.2 向线程函数传递参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如代码清单 2.4 所示，若需向新线程上的函数或可调用对象传递参数，方法相当简单，直接向 `std::thread` 的构造函数增添更多参数即可。\n",
    "\n",
    "不过请务必牢记，线程具有内部存储空间，参数会按照默认方式先复制到该处，新创建的执行线程才能直接访问它们。然后，这些副本被当成临时变量，以右值形式传给新线程上的函数或可调用对象。即便函数的相关参数按设想应该是引用，上述过程依然会发生。例如：\n",
    "\n",
    "> 右值即rvalue，是一类表达式，本质是固定不变的“值”（相对变量之“可变”），不接受赋值，只出现在赋值表达式等号右方。它的特点是没有标识符，无法提取地址，往往是临时变量或匿名变量，仅在其所属语句中有效，满足移动语义。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void f(int i, std::string const& s); \n",
    "\n",
    "std::thread t(f, 3, \"hello\"); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这两行代码借由构造对象 `t` 新创建一个执行线程，它们互相关联，并在新线程上调用 `f(3, \"hello\")`。\n",
    "\n",
    "请注意，尽管函数`f()`的第二个参数属于 `std::string` 类型，但字符串的字面内容仍以指针 `char const*`的形式传入，进入新线程的上下文环境以后，才转换为`std::string` 类型。如果参数是指针，并且指向自动变量（automatic variable），那么这一过程会产生非常重大的影响，举例说明：\n",
    "\n",
    "> 译者注：自动变量即为代码块内声明或定义的局部变量，位于程序的栈空间。请注意，C++11 标准重新引入了 `auto` 关键字，但它的语义与此处完全不同。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void f(int i, std::string const& s); \n",
    "\n",
    "void oops(int some_param) {\n",
    "  char buffer[1024]; // 1\n",
    "  sprintf(buffer, \"%i\", some_param);\n",
    "  std::thread t(f, 3, buffer); // 2\n",
    "  t.detach();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在本例中，向新线程②传递的是指针 `buffer`，指向一个局部数组变量①。\n",
    "\n",
    "我们原本设想，`buffer` 会在新线程内转换成 `std::string` 对象，但在此完成之前，`oops()`函数极有可能已经退出，导致局部数组被销毁而引发未定义行为。这一问题的根源在于：我们本来希望将指针 `buffer` 隐式转换成 `std::string` 对象，再将其用作函数参数，可惜转换未能及时发生，原因是 `std::thread` 的构造函数原样复制所提供的值，并未令其转换为预期的参数类型。\n",
    "\n",
    "解决方法是，在 `buffer` 传入 `std::thread` 的构造函数之前，就把它转化成 `std::string`对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void f(int i, std::string const& s); \n",
    "\n",
    "void not_oops(int some_param) {\n",
    "  char buffer[1024];\n",
    "  sprintf(buffer, \"%i\", some_param);\n",
    "  std::thread t(f, 3, std::string(buffer)); // 使用 std::string 避免悬空指针\n",
    "  t.detach();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与 `oops` 函数相反的情形是，我们真正想要的是非 `const` 引用（non-const reference），而整个对象却被完全复制。但这不可能发生，因为编译根本无法通过。读者可尝试按引用的方式传入一个数据结构，以验证线程能否对其进行更新，例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void update_data_for_widget(widget_id w, widget_data& data); // 1\n",
    "\n",
    "void oops_again(widget_id w) { \n",
    "  widget_data data;  \n",
    "  // note: in instantiation of function template specialization 'std::thread::thread<void (&)(int, widget_data &), int &, widget_data &, void>' requested here\n",
    "  std::thread t(update_data_for_widget, w, data); // 2\n",
    "  display_status();  \n",
    "  t.join();  \n",
    "  process_widget_data(data); \n",
    "} "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据`update_data_for_widget()`函数的声明，第二个参数会以引用方式传入①，可是`std::thread` 的构造函数对此却并不知情，它全然忽略`update_data_for_widget()`函数所期望的参数类型，直接复制我们提供的值②。\n",
    "\n",
    "然而，线程库的内部代码会把参数的副本（于②处由对象 `data` 复制得出，位于新线程的内部存储空间）当成 move-only 型别（只能移动，不可复制），并以右值的形式传递。最终，`update_data_for_widget()`函数调用会收到一个右值作为参数。这段代码会编译失败。因为`update_data_for_widget()`预期接受非`const`引用，我们不能向它传递右值。\n",
    "\n",
    "假使读者知晓 `std::bind()`函数的工作原理（由 C++11 引入），解决方法就显而易见：若需按引用方式传递参数，只要用 `std::ref()`函数加以包装即可。\n",
    "\n",
    "把创建线程的语句改写成："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void update_data_for_widget(widget_id w, widget_data& data);\n",
    "\n",
    "void oops_again(widget_id w) { \n",
    "  widget_data data;  \n",
    "  std::thread t(update_data_for_widget, w, std::ref(data));\n",
    "  display_status();  \n",
    "  t.join();  \n",
    "  process_widget_data(data); \n",
    "} "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "那么，传入 `update_data_for_widget()`函数的就不是变量 `data` 的临时副本，而是指向变量 `data` 的引用，代码遂能成功编译。\n",
    "\n",
    "根据 `std::thread` 的构造函数和 `std::bind()`函数的定义，它们都运用相同的机制进行内部操作。所以如果读者熟识 `std::bind()`函数，那么上述参数传递语义就不足为奇。\n",
    "\n",
    "下面举例说明其意义：若要将**某个类的成员函数设定为线程函数**，我们则应传入一个函数指针，指向该成员函数。此外，我们还要给出合适的对象指针，作为该函数的第一个参数：\n",
    "> 一般地，在调用类的非静态成员函数时，编译器会隐式添加一参数，它是所操作对象的地址，用于绑定对象和成员函数，并且位于所有其他实际参数之前。例如，类 `example` 具有成员函数 `func(int x)`，而 `obj` 是该类的对象，则调用 `obj.func(2)`等价于调用 `example::func(&obj, 2)`。请参阅《深度探索C++对象模型》。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "class X  { \n",
    "  public: \n",
    "    void do_lengthy_work(); \n",
    "};\n",
    "\n",
    "X my_x;\n",
    "\n",
    "std::thread t(&X::do_lengthy_work, &my_x); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上述对象指针由 `my_x` 的地址充当，这段代码将它传递给 `std::thread` 的构造函数，因此新线程会调用 `my_x.do_lengthy_work()`。我们还能为成员函数提供参数：若给 `std::thread` 的构造函数增添第 3 个参数，则它会传入成员函数作为第 1 个参数，以此类推。\n",
    "\n",
    "> 若考虑到对象指针，则成员函数的第 1 个形式参数实质上是它的第 2 个实际参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "值得注意的是，C++11 还引入了另一种传递参数的方式：参数只能移动但不能复制，即数据从某个对象转移到另一个对象内部，而原对象则被“搬空”。\n",
    "\n",
    "这种型别的其中一个例子是 `std::unique_ptr`，它为动态分配的对象提供自动化的内存管理。在任何时刻，对于给定的对象，只可能存在唯一一个 `std::unique_ptr` 实例指向它；若该实例被销毁，所指对象亦随之被删除。通过移动构造（move constructor）函数和移动赋值操作符（move assignment operator），对象的归属权就得以在多个 `std::unique_ptr` 实例间转移（请参阅附录 A.1.1 节，了解移动语义的细节）。移动行为令 `std::unique_ptr` 的源对象（source object）的值变成 `NULL` 指针。\n",
    "\n",
    "函数可以接收这种类型的对象作为参数，也能将它作为返回值，充分发挥其可移动特性，以提升性能。若源对象是临时变量，移动就会自动发生。若源对象是具名变量，则必须通过调用 `std::move()`直接请求转移。下面示范了 `std::move()`函数的用法，我们借助它向线程转移动态对象的归属权："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void process_big_object(std::unique_ptr<big_object>); \n",
    "\n",
    "std::unique_ptr<big_object> p(new big_object); \n",
    "p->prepare_data(42);\n",
    "\n",
    "std::thread t(process_big_object, std::move(p)); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在调用 `std::thread` 的构造函数时，依据 `std::move(p)`所指定的操作，`big_object` 对象的归属权会发生转移，先进入新创建的线程的内部存储空间，再转移给 `process_big_object()`函数\n",
    "> `std::move(p)`未进行移动操作，仅仅将 `p` 转换成右值；创建线程时，发生一次转移（由线程库内部进行复制）；`process_big_object()`开始执行时，再次转移（函数调用复制参数）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 C++标准库中，有几个类的归属权语义与 `std::unique_ptr` 一样，`std::thread` 类就是其中之一。虽然 `std::thread` 类的实例并不拥有动态对象（这与 `std::unique_ptr` 不同），但它们拥有另一种资源：每份实例都负责管控一个执行线程。因为 `std::thread` 类的实例能够移动（movable）却不能复制（not copyable），故此线程的归属权可以在其实例之间转移。这就保证了，对于任一特定的执行线程，任何时候都只有唯一的 `std:::thread` 对象与之关联，还准许程序员在其对象之间转移线程归属权。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.3 移交线程归属权"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "假设读者要编写函数，功能是创建线程，并置于后台运行，但该函数本身不等待线程完结，而是将其归属权向上移交给函数的调用者；或相反地，读者想创建线程，遂将其归属权传入某个函数，由它负责等待该线程结束。两种操作都需要转移线程的归属权。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这正是 `std::thread` 支持移动语义的缘由。2.2 节已介绍过，C++标准库含有不少掌握资源的型别（resource-owning type），如 `std::ifstream` 类和 `std::unique_ptr` 类，它们中的许多却不能复制，`std::thread` 类亦是其中之一。因此，对于一个具体的执行线程，其归属权可以在几个 `std::thread` 实例间转移，如下面的代码所示。此例创建 2 个执行线程和 3个 `std::thread` 实例 `t1、t2、t3`，并将线程归属权在实例之间多次转移。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void some_function();\n",
    "void some_other_function();\n",
    "\n",
    "std::thread t1(some_function);          // ①\n",
    "std::thread t2 = std::move(t1);         // ②\n",
    "t1 = std::thread(some_other_function);  // ③\n",
    "std::thread t3;                         // ④\n",
    "t3 = std::move(t2);                     // ⑤\n",
    "t1 = std::move(t3);                     // ⑥ 该赋值操作会终止整个程序"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "首先，我们启动新线程①，并使之关联 `t1`。接着，构建 `t2`，在其初始化过程中调用 ，将新线程的归属权显式地转移给 `t2` ②。在②之前，`t1` 关联着执行线程，`some_function()`函数在其上运行；及至②处，新线程关联的变换为 `t2`。\n",
    "> `std::move()`仅仅将 `t1` 强制转换成右值，但没有进行移动；真正触发移动行为的是 `t2` 的初始化。\n",
    "\n",
    "在②之前，`t1` 关联着执行线程，`some_function()`函数在其上运行；及至②处，新线程关联的变换为 `t2`。\n",
    "\n",
    "然后，启动另一新线程③，它与一个 `std::thread` 类型的临时对象关联。新线程的归属权随即转移给 `t1`。这里无须显式调用 `std::move()`，因为新线程本来就由临时变量持有，而源自临时变量的移动操作会自动地隐式进行。\n",
    "\n",
    "`t3` 按默认方式构造④，换言之，在创建时，它并未关联任何执行线程。在⑤处，`t2`原本关联的线程的归属权会转移给 `t3`，而 `t2` 是具名变量，故需再次显式调用 `std::move()`，先将其转换为右值。经过这些转移，`t1` 与运行 `some_other_function()`的线程关联，`t2` 没有关联线程，而 `t3` 与运行 `some_function()`的线程关联。\n",
    "\n",
    "在最后一次转移中⑥，运行 `some_function()`的线程的归属权转移到 `t1`，该线程最初由 `t1` 启动。但在转移之时，`t1` 已经关联运行 `some_other_function()`的线程。因此`std::terminate()`会被调用，终止整个程序。该调用在 `std::thread` 的析构函数中发生，目的是保持一致性。2.1.1 节已解释过，在 `std::thread` 对象析构前，我们必须明确：是等待线程完成还是要与之分离。不然，便会导致关联的线程终结。赋值操作也有类似的原则：只要 `std::thread` 对象正管控着一个线程，就不能简单地向它赋新值，否则该线程会因此被遗弃。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::thread` 支持移动操作的意义是，函数可以便捷地向外部转移线程的归属权，示例代码如代码清单 2.5 所示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.5 从函数内部返回 std::thread 对象\n",
    "// https://godbolt.org/z/69sq5eMMn\n",
    "\n",
    "#include <thread>\n",
    "\n",
    "void some_function() {}\n",
    "\n",
    "void some_other_function(int) {}\n",
    "\n",
    "std::thread f() {\n",
    "    void some_function();\n",
    "    return std::thread(some_function);\n",
    "}\n",
    "\n",
    "std::thread g() {\n",
    "    void some_other_function(int);\n",
    "    std::thread t(some_other_function, 42);\n",
    "    return t;\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    std::thread t1 = f();\n",
    "    t1.join();\n",
    "    std::thread t2 = g();\n",
    "    t2.join();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类似地，若归属权可以转移到函数内部，函数就能够接收 `std::thread` 实例作为按右值传递的参数，如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "void f(std::thread t);\n",
    "\n",
    "void g() {\n",
    "    void some_function();\n",
    "    f(std::thread(some_function));\n",
    "    std::thread t(some_function);\n",
    "    f(std::move(t));\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::thread `支持移动语义给我们带来了不少好处。一个好处是，对代码清单 2.3 的`thread_guard` 类稍作修改，我们就能用其构建线程，并将线程交由该类掌管。\n",
    "\n",
    "另一个好处是，只要线程归属权转移给某个 `thread_guard` 对象，其他对象就无法执行汇合或分离操作。\n",
    "\n",
    "反之，假设 `std::thread` 不支持移动语义，那么，万一 `thread_guard` 对象的生存期超出它管控的线程，将导致种种不良后果，事实上支持移动语义免除了这些麻烦。\n",
    "\n",
    "我们来设计一个新类 `scoped_thread`：它的首要目标，是在离开其对象所在的作用域前，确保线程已经完结。代码清单 2.6 给出了实现代码，并附带简单用例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.6 scoped_thread 类及其用例\n",
    "// https://godbolt.org/z/axEGrz7Pv\n",
    "\n",
    "#include <thread>\n",
    "#include <utility>\n",
    "#include <stdexcept>\n",
    "\n",
    "class scoped_thread {\n",
    "    std::thread t;\n",
    "\n",
    "   public:\n",
    "    explicit scoped_thread(std::thread t_) : t(std::move(t_)) { // ①\n",
    "        if (!t.joinable()) throw std::logic_error(\"No thread\"); // ②\n",
    "    }\n",
    "    ~scoped_thread() { t.join(); } // ③\n",
    "    scoped_thread(scoped_thread const&) = delete;\n",
    "    scoped_thread& operator=(scoped_thread const&) = delete;\n",
    "};\n",
    "\n",
    "void do_something(int& i) { ++i; }\n",
    "\n",
    "struct func { // ⑥\n",
    "    int& i;\n",
    "\n",
    "    func(int& i_) : i(i_) {}\n",
    "\n",
    "    void operator()() {\n",
    "        for (unsigned j = 0; j < 1000000; ++j) {\n",
    "            do_something(i);\n",
    "        }\n",
    "    }\n",
    "};\n",
    "\n",
    "void do_something_in_current_thread() {}\n",
    "\n",
    "void f() {\n",
    "    int some_local_state;\n",
    "    scoped_thread t(std::thread(func(some_local_state))); // ④\n",
    "\n",
    "    do_something_in_current_thread();\n",
    "} // ⑤\n",
    "\n",
    "int main() { f(); }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此例与代码清单 2.3 相似，不过，这里直接向 `scoped_thread` 的构造函数传入新线程④，而非为其创建单独的具名变量。当起始线程运行至 `f()`末尾时⑤，对象 `t` 就会被销毁，与 `t` 关联的线程随即和起始线程汇合③，该关联线程于④处发起，传递给 `scoped_thread`的构造函数①，构建出对象 `t`。在代码清单 2.3 的 `thread_guard` 类中，其析构函数须判断线程是否依然可汇合，而本例中，我们在构造函数中判断，若不可以就抛出异常②。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "曾经有一份 C++17 标准的备选提案，主张引入新的类 `joining_thread`，它与 `std::thread`类似，但只要其执行析构函数，线程即能自动汇合，这点与 `scoped_thread` 非常像。可惜C++标准委员会未能达成共识，结果 C++17 标准没有引入这个类，后来它改名为`std::jthread`，依然进入了 C++20 标准的议程（现已被正式纳入 C++20 标准）。除去这些，实际上 `joining_thread` 类的代码相对容易编写，代码清单 2.7 展示了一个可行的实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.7 joining_thread 类\n",
    "// https://godbolt.org/z/1jYoK9vqh\n",
    "\n",
    "#include <thread>\n",
    "\n",
    "class joining_thread {\n",
    "    std::thread t;\n",
    "\n",
    "   public:\n",
    "    joining_thread() noexcept = default;\n",
    "    template <typename Callable, typename... Args>\n",
    "    explicit joining_thread(Callable&& func, Args&&... args)\n",
    "        : t(std::forward<Callable>(func), std::forward<Args>(args)...) {}\n",
    "    explicit joining_thread(std::thread t_) noexcept : t(std::move(t_)) {}\n",
    "    joining_thread(joining_thread&& other) noexcept : t(std::move(other.t)) {}\n",
    "\n",
    "    joining_thread& operator=(joining_thread&& other) noexcept {\n",
    "        if (joinable()) join();\n",
    "        t = std::move(other.t);\n",
    "        return *this;\n",
    "    }\n",
    "\n",
    "    joining_thread& operator=(std::thread other) noexcept {\n",
    "        if (joinable()) join();\n",
    "        t = std::move(other);\n",
    "        return *this;\n",
    "    }\n",
    "\n",
    "    ~joining_thread() noexcept {\n",
    "        if (joinable()) join();\n",
    "    }\n",
    "\n",
    "    void swap(joining_thread& other) noexcept { t.swap(other.t); }\n",
    "    std::thread::id get_id() const noexcept { return t.get_id(); }\n",
    "    bool joinable() const noexcept { return t.joinable(); }\n",
    "    void join() { t.join(); }\n",
    "    void detach() { t.detach(); }\n",
    "    std::thread& as_thread() noexcept { return t; }\n",
    "    const std::thread& as_thread() const noexcept { return t; }\n",
    "};\n",
    "\n",
    "void do_something(int& i) { ++i; }\n",
    "\n",
    "struct func {\n",
    "    int& i;\n",
    "\n",
    "    func(int& i_) : i(i_) {}\n",
    "\n",
    "    void operator()() {\n",
    "        for (unsigned j = 0; j < 1000000; ++j) {\n",
    "            do_something(i);\n",
    "        }\n",
    "    }\n",
    "};\n",
    "\n",
    "void do_something_in_current_thread() {}\n",
    "\n",
    "void f() {\n",
    "    int some_local_state;\n",
    "    joining_thread t(std::thread(func(some_local_state)));  // ④\n",
    "\n",
    "    do_something_in_current_thread();\n",
    "}\n",
    "\n",
    "int main() { f(); }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因为 `std::thread `支持移动语义，所以只要容器同样知悉移动意图（例如，符合新标准的 `std::vector<>`），就可以装载 `std::thread `对象。因此我们可以写出下列代码，生成多个线程，然后等待它们完成运行。\n",
    "\n",
    "> 即 move-aware，指容器能够把元素移动或复制到其内部，特别是可以正确处理只移动对象，并且能在容器内部进行移动操作。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.8 生成多个线程，并等待它们完成运行\n",
    "// https://godbolt.org/z/fxEjP9Gz9\n",
    "\n",
    "#include <algorithm>\n",
    "#include <functional>\n",
    "#include <thread>\n",
    "#include <vector>\n",
    "\n",
    "void do_work(unsigned id) {}\n",
    "\n",
    "void f() {\n",
    "    std::vector<std::thread> threads;\n",
    "    for (unsigned i = 0; i < 20; ++i) {\n",
    "        threads.push_back(std::thread(do_work, i)); // 生成线程\n",
    "    }\n",
    "\n",
    "    std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 依次在各线程上调用 join()函数\n",
    "    // for(auto& entry: threads) entry.join(); \n",
    "}\n",
    "\n",
    "int main() { f(); }"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "若要运用多线程切分某算法的运算任务，往往要求采取以上方式；必须等所有线程完成运行后，运行流程才能返回到调用者。\n",
    "\n",
    "代码清单 2.8 的代码结构简单，说明了每个线程上的任务都是*自含*的，且它们操作共享数据的结果单纯地由*副作用*产生。\n",
    "\n",
    "> self-contained，指线程内数据齐全，可独立完成子任务，而不依赖外部数据。\n",
    "\n",
    "> 这里的“副作用”是 C++标准中规定的特定术语，意思是执行状态的改变，譬如访问 volatile变量，修改对象，进行 I/O 访问，以及调用某个造成副作用的函数，等等，这些操作都有可能影响其他线程的状态。\n",
    "\n",
    "\n",
    "假如`f()`需要向其调用者返回值，而它又依赖于所有线程的运算结果，那么，正如前文所述，只有等到全部线程终止，我们才能核查共享数据，以推算出该返回值。还有别的方法可在线程间传递运算结果，第4章将对其进行讨论。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们把 `std::thread` 对象存放到 `std::vector` 容器内，向线程管控的自动化迈进了一步：若要为多个线程分别直接创建独立变量，还不如将它们集结成组，统一处理。\n",
    "\n",
    "读者可以更上一层楼，按运行时实际所需的数量动态创建线程。但代码清单 2.8 创建线程的数量则始终固定，我们不必硬搬。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.4 在运行时选择线程数量"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本节需要借用 C++标准库的 `std::thread::hardware_concurrency()`函数，它的返回值是一个指标，表示程序在各次运行中可真正并发的线程数量。例如，在多核系统上，该值可能就是 CPU 的核芯数量。这仅仅是一个指标，若信息无法获取，该函数则可能返回0。\n",
    "\n",
    "虽然如此，若要用多线程分解完整的任务，该值仍不失为有用的指标。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码清单 2.9 是并行版的 `std::accumulate()`的简单实现，本例意在说明基本思路。在实际的代码中，读者很可能直接使用并行版的 `std::reduce()`函数（详细说明见第 10 章），而不会亲自手动实现。\n",
    "\n",
    "代码清单 2.9 将工作分派给各线程，并设置一个限定量，每个线程所负责的元素数量都不低于该限定量，从而防止创建的线程过多，造成额外开销。\n",
    "\n",
    "请注意，代码清单 2.9 的实现假设没有任何操作抛出异常，但在现实环境中有可能抛出异常：例如，若未能成功启动新的执行线程，`std::thread` 的构造函数就会抛出异常。在算法中加入异常处理偏离了本例主旨，我们将其留待第 8 章深入探讨。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "// 2.9 并行版的 std::accumulate()的简单实现\n",
    "// https://godbolt.org/z/5xoPb8EbM\n",
    "\n",
    "#include <algorithm>\n",
    "#include <functional>\n",
    "#include <iostream>\n",
    "#include <numeric>\n",
    "#include <thread>\n",
    "#include <vector>\n",
    "\n",
    "template <typename Iterator, typename T>\n",
    "struct accumulate_block {\n",
    "    void operator()(Iterator first, Iterator last, T& result) {\n",
    "        result = std::accumulate(first, last, result);\n",
    "    }\n",
    "};\n",
    "\n",
    "template <typename Iterator, typename T>\n",
    "T parallel_accumulate(Iterator first, Iterator last, T init) {\n",
    "    unsigned long const length = std::distance(first, last);\n",
    "\n",
    "    if (!length) return init; // 1: 假如传入的区间为空，就直接返回初始值\n",
    "\n",
    "    unsigned long const min_per_thread = 25;\n",
    "    unsigned long const max_threads =\n",
    "        (length + min_per_thread - 1) / min_per_thread; // 2: 元素总量除以每个线程处理元素的最低限定量，得出线程的最大数量, 为了预防不合理地发起过多线程\n",
    "\n",
    "    unsigned long const hardware_threads = std::thread::hardware_concurrency();\n",
    "\n",
    "    unsigned long const num_threads =\n",
    "        std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); // 3: 对比算出的最小线程数量和硬件线程数量，较小者即为实际需要运行的线程数量\n",
    "\n",
    "    unsigned long const block_size = length / num_threads; // 4: 各线程需分担的元素数量\n",
    "\n",
    "    std::vector<T> results(num_threads);\n",
    "    std::vector<std::thread> threads(num_threads - 1); // 5: 主线程本身就算一个\n",
    "\n",
    "    // 通过简单的循环，整个目标区间在形式上被切成多个小块, 针对每个小块逐一创建线程\n",
    "    Iterator block_start = first;\n",
    "    for (unsigned long i = 0; i < (num_threads - 1); ++i) {\n",
    "        Iterator block_end = block_start;\n",
    "        std::advance(block_end, block_size); // 6: 轮循环中，迭代器 block_end 前移至当前小块的结尾，新线程就此启动\n",
    "                                             // std::advance: 将迭代器向前或向后移动指定的步数\n",
    "        threads[i] = std::thread(accumulate_block<Iterator, T>(), block_start,\n",
    "                                 block_end, std::ref(results[i]));  // 7: 计算该小块的累加结果\n",
    "        block_start = block_end; // 8: 下一小块的起始位置即为本小块的末端\n",
    "    }\n",
    "    accumulate_block<Iterator, T>()(block_start, last,\n",
    "                                    results[num_threads - 1]); // 9: 发起全部线程后，主线程随之处理最后一个小块\n",
    "                                                               // 我们知道，无论最后的小块含有多少元素，迭代器 last 必然指向其结尾，这正好解决了上述无法整除的问题\n",
    "\n",
    "    std::for_each(threads.begin(), threads.end(),\n",
    "                  std::mem_fn(&std::thread::join)); // 10\n",
    "    // for(auto& entry: threads) entry.join(); // 10: 在一个循环里等待前面所有生成的线程\n",
    "\n",
    "    return std::accumulate(results.begin(), results.end(), init); // 11: std::accumulate()函数累加中间结果\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    std::vector<int> vi;\n",
    "    for (int i = 0; i < 10; ++i) {\n",
    "        vi.push_back(10);\n",
    "    }\n",
    "    int sum = parallel_accumulate(vi.begin(), vi.end(), 5);\n",
    "    std::cout << \"sum=\" << sum << std::endl;\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "代码虽然不短，却明快直观。\n",
    "\n",
    "参与计算的元素位于目标区间内，范围由参数 `first`和 `last` 指定。假如传入的区间为空①，就直接返回初始值，该值由参数 `init` 给出。这样区间至少含有一个元素。然后，我们将元素总量除以每个线程处理元素的最低限定量，得出线程的最大数量②。这是为了预防不合理地发起过多线程。\n",
    "\n",
    "譬如，代码在 32 核机器上运行，而区间内仅有 5 个元素，那根本不必创建 32 个线程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对比算出的最小线程数量和硬件线程数量，较小者即为实际需要运行的线程数量③。\n",
    "\n",
    "硬件支持的线程数量有限，运行的线程数量不应超出该限度（超出的情况称为线程过饱和，即 oversubscription），因为线程越多，上下文切换越频繁，导致性能降低。如果 `std::thread::hardware_concurrency()`返回 `0`，我们便要自己选择一个数量，这里我们设成 2。我们不希望运行太多线程，否则，若代码在单核机器上运行，就会令所有任务变慢；但我们同样不想线程数量过少，因为那样无从发挥可用的并发性能。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "接着，将目标区间的长度除以线程数量，得出各线程需分担的元素数量④。读者可能在意无法整除的情况，大可不必，我们稍后将解释。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "线程数量至此已确定妥当⑤，于是我们创建 `std::vector<T>`容器存放中间结果，创建`std::vector<std::thread>`容器装载线程⑤。请注意，需要发起的线程数量比前面所求的`num_threads` 少一个，因为主线程本身就算一个（`parallel_accumulate()`函数在其上运行）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过简单的循环，整个目标区间在形式上被切成多个小块，我们针对每个小块逐一创建线程：每轮循环中，迭代器 `block_end` 前移至当前小块的结尾⑥，新线程就此启动，计算该小块的累加结果⑦。下一小块的起始位置即为本小块的末端⑧。\n",
    "> 这里利用了 STL 容器区间的左闭右开的设计特性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "发起全部线程后，主线程随之处理最后一个小块⑨。我们知道，无论最后的小块含有多少元素，迭代器 `last` 必然指向其结尾，这正好解决了上述无法整除的问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后小块的累加一旦完成，我们就在一个循环里等待前面所有生成的线程⑩，这与代码清单 2.8 类似。最后，调用 `std::accumulate()`函数累加中间结果 。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在结束本例前，值得指出，某些类型 `T` 的加法操作不满足结合律（如 `float` 或 `double`类型），因为整个目标区间被分块累加，所以 `parallel_accumulate()`的结果可能与`std::accumulate()`有异。另外，本例对迭代器的要求也略微更严格：它们至少必须是前向迭代器（forward iterator），而 `std::accumulate()`函数可以接受单程输入迭代器（single-pass input iterator）。并且，为了创建名为 `results` 的 `vector` 容器来存放中间结果，类型 `T` 必须支持默认构造（default-constructible）。为了配合并行算法，这种种针对前提条件的变更是屡见不鲜：算法自身的特性决定了，必须有所改动才可能实现并行，而这会影响到前提条件和最终结果。第 8 章将更深入地分析并行算法的实现，第 10 章则会介绍 C++17提供的标准算法（其中包括并行版的 `std::reduce()`，它与这里讲解的 `parallel_accumulate()`等价）。另外，值得注意的是，因为无法从线程直接返回值，所以我们必须向线程传入引用，指向目标是执行结果的相关数据项，该数据项存放在名为 `results` 的 `vector` 容器内。第 4 章将说明，我们可以运用 `future` 通过别的方式从线程返回结果。\n",
    "\n",
    "> 不满足结合律: 指精度损失，考虑数量级差别巨大的两个值，以 4.5678 和 9.0e-123 为例，它们相加之和的理论值应该是 4.5678 + 9.0e-123；若用 C++的内建 `double` 类型进行运算，其小数部分的最末端，因精度有限而被截断丢弃，实际结果为 `4.5678`，导致产生误差。详细分析请参阅计算机硬件体系结构的相关资料。\n",
    "\n",
    "> 结果有异: 本例的代码实现按分块方式运算，而 STL 的众多实现则是全范围顺次累加，因此两者累积误差不同。详细分析请参阅数值分析的相关资料。\n",
    "\n",
    "> 必须向线程传入引用: 参见代码清单 2.9 中仿函数 `accumulate_block()`的定义，注意成员函数 `operator()()`的最后一个参数\n",
    "\n",
    "> 数据项存放在名为 `results` 的 `vector` 容器内: 参见代码清单 2.9 的⑦处，请注意其最后一个参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在上例中，在各线程启动之际，它们所需的全部信息就得以传入，其中包括计算结果的存储位置。然而，事情不会总是如此简单、顺利。不少情况下，识别线程是多线程处理的必要手段之一。我们可以传入某个号码作为线程 ID，例如在代码清单 2.8 中，把变量 `i` 的值赋给线程 ID。但是，若某函数需获取线程 ID，却位处调用栈的深层，或者该函数调用需要由任意线程发起，这种做法就不太方便。C++标准委员会在设计标准库时预见了这一需求，所以每个线程都内建了唯一的线程 ID。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.5 识别线程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "线程 ID 所属型别是 `std::thread::id`，它有两种获取方法。\n",
    "- 首先，在与线程关联的`std::thread` 对象上调用成员函数 `get_id()`，即可得到该线程的 ID。\n",
    "- 如果 `std::thread` 对象没有关联任何执行线程，调用 `get_id()`则会返回一个 `std::thread::id` 对象，它按默认构造方式生成，表示“线程不存在”。其次，当前线程的 ID 可以通过调用 `std::this_thread::get_id()`获得，函数定义位于头文件`<thread>`内。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::thread::id` 型别的对象作为线程 ID，可随意进行复制操作或比较运算；否则，它们就没有什么大的用处。如果两个 `std::thread::id` 型别的对象相等，则它们表示相同的线程，或者它们的值都表示“线程不存在”；如果不相等，它们就表示不同的线程，或者当中一个表示某个线程，而另一个表示“线程不存在”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C++标准库容许我们随意判断两个线程 ID 是否相同，没有任何限制；`std::thread::id`型别具备全套完整的比较运算符，比较运算符就所有不相等的值确立了全序（total order）关系\n",
    "\n",
    "> 全序关系是关于集合的数学概念，满足 3 个性质：反对称、传递和完全。整数之间的比较关系“≤”，即“小于等于”，就是一种全序关系。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "就所有不相等的 `std::thread::id` 的值，比较运算符确立了全序关系，它们的行为与我们的预期相符：若 `a<b` 且 `b<c`，则有 `a<c`，以此类推。标准库的 hash 模板能够具体化成`std::hash<std::thread::id>`，因此 `std::thread::id` 的值也可以用作新标准的无序关联容器（unordered associative container）的键值。\n",
    "\n",
    "> 指 C++11 标准引入的 4 种新的 STL 容器：`std::unordered_set`、`std::unordered_map`、`std::unordered_multiset`、`std::unordered_multimap`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`std::thread::id` 实例常用于识别线程，以判断它是否需要执行某项操作。如果在运行过程中用线程切分算法任务，如代码清单 2.9，那么主线程负责发起其他线程，它可能需要承担稍微不一样的工作。若需在该例中识别主线程，我们可以在发起其他线程之前，在主线程中预先保存 `std::this_thread::get_id()`的结果；算法的核心部分对全体线程毫无区别，由它们共同运行，只要对比各线程预存的值和自身的线程 ID，就能做出判断："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::thread::id master_thread;\n",
    "void some_core_part_of_algorithm() {\n",
    "    if (std::this_thread::get_id() == master_thread) {\n",
    "        do_master_thread_work();\n",
    "    }\n",
    "    do_common_work();\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "另外，我们还能令数据结构聚合 `std::thread::id` 型别的成员，以保存当前线程的 ID，作为操作的要素以控制权限。凡涉及该数据结构的后续操作，都要对比执行线程的 ID与预存的 ID，从而判断该项操作是否得到了许可，或是否按要求进行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "类似地，在一些场景中，某些特定数据需要与线程关联，但其他关联方式却不适合（如隶属于线程的局部存储），这时就可以采用关联容器，线程 ID 则作为其键值。举例而言，主控线程可以利用这种容器，存储每个受控线程的相关信息，或存储信息以便在线程间互相传递。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "绝大多数情况下，`std::thread::id` 足以作为通用 ID。除非我们把线程 ID 另作他用，令它的值含有其他语义（如充当数组索引），否则没必要使用别的标识方法。我们甚至可以把 `std::thread::id` 的实例写到输出流，如 `std::cout`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "std::cout<<std::this_thread::get_id(); "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "确切的输出内容严格依赖于实现，C++标准只是保证了比较运算中相等的线程 ID应产生一样的输出，而不相等的线程 ID 则产生不同的输出。这个特性的主要用途是调试除错和记录日志。然而线程 ID 的值本身并不具备语义层面的意义。至此，识别线程已经全部介绍完毕。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.6 小结"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "name": "C++17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
